import java.net.*;
import java.io.*;
import java.util.*;

public class ClientCommunication {
	/**
	 * The player ID the local player have.
	 */
	public static int localID = 0;
    /**
     * An int representing the CentralServer
	 */
	private static int CENTRAL_SERVER = 0;
	/**
	 * An int representing the GameServer
	 */
	private static int GAME_SERVER = 1;
	/**
	 *  A socket connected to the current CentralServer
	 */
	private EasySocket centralServerSocket;
	private EasySocket centralServerSocketAdded = null;
	private String centralServerParameters = "";
	private String appEngineFile = "";
	private String appEngineParameters = "";
	/**
	 * A socket connected to the current GameServer.
	 */
	private EasySocket gameServerSocket;

	private String centralServerAddress;
	private int centralServerPort;
	private String localAddress;

	private boolean connectedToGameServer = false;
	private boolean connectingToGameServer = false;

	private static final String HTTP_HEADER = "POST /centralserver.php HTTP/1.1\r\n"+
												"Host: multitris.swapgeek.com\r\n";
	private static final String HTTP_HEADER_APPENGINE = "POST /services/server/# HTTP/1.1\r\n"+
														"Host: multitris.appspot.com\r\n";

	public boolean useHttpCentralServer = true;
	public boolean useAppEngineServer = false;

	/**
	 * Creates a new object to handle communication to a central server and/or
	 * a game server. When created, it will try to find the local address of this
	 * machine. No socket connections will be made.
	 */
	public ClientCommunication(String centralServerAddress, int centralServerPort) {
		this.centralServerAddress = centralServerAddress;
		this.centralServerPort = centralServerPort;

		useHttpCentralServer = (centralServerPort == 80);

		Enumeration<NetworkInterface> enumNet = null;
		try {
			enumNet = NetworkInterface.getNetworkInterfaces();
		} catch (SocketException e) {
			e.printStackTrace();
		}
		while (enumNet != null && enumNet.hasMoreElements()) {
			Enumeration<InetAddress> enumInet = enumNet.nextElement().getInetAddresses();
			while (enumInet.hasMoreElements()) {
				localAddress = enumInet.nextElement().getHostAddress();
			}
		}
		if (localAddress != null) {
			System.out.println("Local address: "+localAddress);
		} else {
			System.out.println("No local address found.");
		}
	}

	/**
	 * This main method is only for debugging and test purposes.
	 */
	public static void main(String args[]) {
		ClientCommunication cc = new ClientCommunication("multitris.swapgeek.com", 80);//"fb4.fragbite.com", 5399);
		Multitris.clientCommunication = cc;
		if (true) { // CentralServer test
			cc.requestServerList();
			while (true) {
				LinkedList<String[]> arr = cc.getServerList();
				if (arr != null) {
					for (String[] str : arr) {
						System.out.println("Server: "+Arrays.toString(str));//str[0]+" - "+str[1]+" - "+str[2]);
					}
					return;
				}
			}
		} else { // GameServer test
			cc.connectToGameServer("localhost", 5398);
			int i=0;
			while (true) {
				i+=500;
				if (i >= 4000) {
					i = 0;
					cc.sendAction("ping");
				}
				String[] arr = cc.getActions();
				for (String action : arr) {
					System.out.println("Action: "+action);
					if (action.startsWith("id ")) {
						cc.sendAction("name Don"); // on id, send the name to the server
					}
				}
				System.out.println(".");
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
				}
			}
		}
	}

	/**
	 * Initiate the downloading of the information of a single server
	 *
	 * @param centralServerAddress The address of the centralServerAddress
	 * @param port The port of the central server
	 * @param serverName The name of the server to get information about
	 */
    public void requestServerInformation(String serverName) {
    	centralServerParameters = "get "+serverName;
		appEngineFile = "get_server";
		appEngineParameters = "server_name="+serverName;
		ThreadedEasySocket threadSocket = new ThreadedEasySocket(centralServerAddress, centralServerPort, false);
		threadSocket.start();
    }

	/**
	 * Initiate the downloading of the list of active game servers from the central server
	 *
	 * @param centralServerAddress The address of the centralServerAddress
	 * @param port The port of the central server
	 */
    public void requestServerList() {
		centralServerSocket = null;
		centralServerParameters = "get";
		appEngineFile = "get_list";
		appEngineParameters = "";
		ThreadedEasySocket threadSocket = new ThreadedEasySocket(centralServerAddress, centralServerPort, false);
		threadSocket.start();
    }

	/**
	 * Connects to the central server and notifys that there is a game server on this
	 * particular address. Will give away the local address of the machine in case of
	 * other players that can't connect using the global address.
	 *
	 * @param numberOfPlayers the maximum number of players that can connect to this server
	 * @param privateGame if true, then this game will never show on the public server list.
	 *						All players that want to connect to this server must enter the
	 *						gamename manually.
	 * @param gameName the identifier for this game server to distinguish it from others.
	 */
	public void addToServerList(int numberOfPlayers, boolean privateGame, String gameName) {
		centralServerSocketAdded = null;
		centralServerParameters = "add "+numberOfPlayers+" "+(privateGame ? "private" : "public")+" "+localAddress+" "+gameName;
		appEngineFile = "add_server";
		appEngineParameters = "number_of_players="+numberOfPlayers+"&public_access="+(!privateGame)+"&local_address="+localAddress+"&game_name="+gameName;
		ThreadedEasySocket threadSocket = new ThreadedEasySocket(centralServerAddress, centralServerPort, false);
		threadSocket.start();
	}

	/**
	 * Sends a ping to the central server so it won't remove the game server created from
	 * this machine. If the central server doesn't recieve any messages from a client which
	 * have added itself as a game server, it will be marked as a timeout and have the
	 * server removed from the list.
	 */
	public void refreshAddedServer() {
		if (useHttpCentralServer) {
			centralServerParameters = "update";
			appEngineFile = "update_server";
			appEngineParameters = "";
			ThreadedEasySocket threadSocket = new ThreadedEasySocket(centralServerAddress, centralServerPort, false);
			threadSocket.start();
		} else {
			if (centralServerSocketAdded != null) {
				centralServerSocketAdded.println("ping");
				centralServerSocketAdded.flush();
			}
		}
	}

	public void removeFromCentralServer() {
		if (useHttpCentralServer) {
			centralServerParameters = "remove";
			appEngineFile = "remove_server";
			appEngineParameters = "";
			ThreadedEasySocket threadSocket = new ThreadedEasySocket(centralServerAddress, centralServerPort, false);
			threadSocket.start();
		} else {
			if (centralServerSocketAdded != null) {
				centralServerSocketAdded.println("remove");
				centralServerSocketAdded.flush();
			}
		}
	}

	public void setActiveOnCentralServer() {
		if (useHttpCentralServer) {
			centralServerParameters = "setactive";
			appEngineFile = "set_active";
			appEngineParameters = "";
			ThreadedEasySocket threadSocket = new ThreadedEasySocket(centralServerAddress, centralServerPort, false);
			threadSocket.start();
		} else {
			if (centralServerSocketAdded != null) {
				centralServerSocketAdded.println("setactive");
				centralServerSocketAdded.flush();
			}
		}
	}

	public void sendScoreToCentralServer(String gameName, ArrayList<String> players, int score, int level, int time) {
		if (useHttpCentralServer) {
			StringBuilder playerstring = new StringBuilder();
			for(String s : players) {
				playerstring.append("\\"+s);
			}
			centralServerParameters = "score "+score+" "+level+" "+time+" "+gameName+"\\"+playerstring.toString().substring(1);
			ThreadedEasySocket threadSocket = new ThreadedEasySocket(centralServerAddress, centralServerPort, false);
			threadSocket.start();
		}
	}

	/**
	 * Download the list of active game servers from the central server
	 *
	 * @return a list containing information about the active game servers.
	 *         if the centralServer is not ready null will be returned.
	 */
    public LinkedList<String[]> getServerList() {
    	LinkedList<String[]> gameList = null;
		if (centralServerSocket != null) {
			while (centralServerSocket.hasNextLine()) {
				if (useHttpCentralServer || useAppEngineServer) {

					String line2 = centralServerSocket.nextLine();
					//System.out.println("Line in: "+line2);
					// skip all http header lines
					if (!line2.startsWith("list ")) continue;

					if (gameList == null) {
						gameList = new LinkedList<String[]>();
					}

					Scanner scanner = new Scanner(line2);
					scanner.next();
					int num = scanner.nextInt();

					System.out.println("line: '"+line2+"'"); // should be "list"
					System.out.println("num: '"+num+"'");
					int serverCount = num;//(num.equals("") ? 0 : Integer.parseInt(num));
					for (int i=0; i<serverCount && centralServerSocket.hasNextLine(); i++) {
						String line = centralServerSocket.nextLine();
						System.out.println("line: "+line);
						if (line.equals("")) {
							i--;
						} else {
							if (line.length() > 0) {
								gameList.addLast(line.split(" ", 4));
							}
						}
					}

				} else {
					if (gameList == null) {
						gameList = new LinkedList<String[]>();
					}

					String line2 = centralServerSocket.nextLine();
					Scanner scanner = new Scanner(line2);
					scanner.next();
					int num = scanner.nextInt();

					System.out.println("line: '"+line2+"'"); // should be "list"
					System.out.println("num: '"+num+"'");
					int serverCount = num;//(num.equals("") ? 0 : Integer.parseInt(num));
					for (int i=0; i<serverCount && centralServerSocket.hasNextLine(); i++) {
						String line = centralServerSocket.nextLine();
						System.out.println("line: "+line);
						if (line.equals("")) {
							i--;
						} else {
							if (line.length() > 0) {
								gameList.addLast(line.split(" ", 4));
							}
						}
					}
				}
			}
		}
    	return gameList;
    }


	/**
	 * Connect to a given GameServer.
	 *
	 * @param gameServerAddress The address of the GameServer.
	 * @param port The port of the GameServer.
	 * @return true if a connection was established to the GameServer.
	 */
    public boolean connectToGameServer(String gameServerAddress, int port) {
		connectedToGameServer = false;
		connectingToGameServer = true;
		ThreadedEasySocket game = new ThreadedEasySocket(gameServerAddress, port, true);
		game.start();
		return true;
    }

	/**
	 * Connect to a given GameServer.
	 *
	 * @param gameServerAddress The address of the GameServer.
	 * @param port The port of the GameServer.
	 * @return true if a connection was established to the GameServer.
	 */
    public boolean connectToGameServer(String gameServerAddress, int port, boolean blocking) {
		if (blocking) {
			try {
				connectingToGameServer = true;
				connectedToGameServer = false;
				gameServerSocket = new EasySocket(gameServerAddress, port);
				connectedToGameServer = true;
				return true;
			} catch (IOException e) {
				e.printStackTrace();
				connectedToGameServer = false;
				connectingToGameServer = false;
				return false;
			}
		} else {
			return connectToGameServer(gameServerAddress, port);
		}
    }

	public boolean isConnectingToGameServer() {
		return connectingToGameServer;
	}

	public boolean isConnectedToGameServer() {
		return connectedToGameServer;
	}

	/**
	 * Disconnect from the GameServer.
	 */
    public void disconnectFromGameServer() {
		if (gameServerSocket != null) {
			sendAction("disconnect");
    	    gameServerSocket.close();
		}
    }

	/**
	 * Sends an action to the current GameServer.
	 * This method is called by the doInputAction methods of the GameSessionState
	 * and LobbyState classes.
	 *
	 * @param action The action to send
	 */
    public void sendAction(String action) {
		if (gameServerSocket != null) {
			gameServerSocket.println(action);
			gameServerSocket.flush();
			if (action.length() < 500) {
				System.out.println("Sending: "+action);
			} else {
				System.out.println("Sending: "+action.substring(0, 500)+" ...");
			}
		}
    }

	/**
	 * Gets any new actions sent by the game server. Actions queued after a
	 * game started message will be postponed to the next call of getActions(),
	 * just to ensure that real game actions are forwarded to the correct state.
	 * This method is called by the update loop of the ApplicationStateManager.
	 *
	 * @return list of actions
	 */
    public String[] getActions() {
    	LinkedList<String> actionList = new LinkedList<String>();
		while (connectedToGameServer && gameServerSocket.hasNextLine()) {
			String action = gameServerSocket.nextLine();
			actionList.addLast(action);
			if (action.equals("gamestarted") || action.startsWith("state")) {
				// Both these actions will change the clients state, so break the loop.
				break;
			}
		}
        return actionList.toArray(new String[0]);
    }

	public void setGameServer(EasySocket socket) {
		gameServerSocket = socket;
		connectedToGameServer = (socket != null);
		connectingToGameServer = false;
	}

	public void setCentralServer(EasySocket socket) {
		centralServerSocket = socket;
		centralServerSocketAdded = socket;
		System.out.println("PARAM: "+centralServerParameters);
		if (socket != null) {
			if (useAppEngineServer) {
				String uri = HTTP_HEADER_APPENGINE.replace("#", appEngineFile);
				socket.println(uri+"Content-Length: "+appEngineParameters.length()+"\r\n\r\n"+appEngineParameters);
			} else {
				if (useHttpCentralServer) {
					socket.println(HTTP_HEADER+"Content-Length: "+centralServerParameters.length()+"\r\n\r\n"+centralServerParameters);
				} else {
					socket.println(centralServerParameters);
				}
			}
		}
		//isConnected = false;
		//isConnecting = false;
	}
}

class ThreadedEasySocket extends Thread {
	String serverAddress;
	int port;

	boolean gameServer;

	public ThreadedEasySocket(String serverAddress, int port, boolean gameServer) {
		this.serverAddress = serverAddress;
		this.port = port;
		this.gameServer = gameServer;
	}

	public void run() {
		EasySocket socket = null;
		try {
			socket = new EasySocket(serverAddress, port, 3000);
		} catch (IOException e) {
		}

		if (gameServer) {
			Multitris.clientCommunication.setGameServer(socket);
		} else if (!gameServer) {
			Multitris.clientCommunication.setCentralServer(socket);
		}
	}
}
